如果你來自 C++ 世界,你對多執行緒的印象大概是:
“能編譯就能跑,只是結果可能不對。”
Rust 的哲學剛好相反:
“能跑就表示結果一定對——因為不對的根本不能編譯。”
Rust 的「跨執行緒安全」靠兩個核心 trait 保證:
這兩個 trait 幾乎是 Rust 並行模型的憲法。
Send:如果型別 T 是 Send,表示它的擁有權可以安全移交到另一條執行緒。Sync:如果型別 T 是 Sync,表示你可以同時在多執行緒間「共享 &T(不可變借用)」。用公式表示:
T: Send      → 可跨執行緒 move
&T: Sync     → 可跨執行緒共享引用
例如:
use std::thread;
let v = vec![1, 2, 3];
thread::spawn(move || {
    println!("{:?}", v);
}).join().unwrap(); // OK
Vec<T> 是 Send 的,所以可以被 move 進另一條 thread。
但以下這段會 編譯失敗:
use std::rc::Rc;
use std::thread;
let r = Rc::new(42);
thread::spawn(move || {
    println!("{r}");
});
錯誤訊息:
Rc<i32> cannot be sent between threads safely
因為 Rc<T> 不是 Send(它的引用計數不是 atomic),如果多執行緒同時操作,會造成 race condition。Rust 不允許這樣的型別跨線。
你不需要手動實作,大部分標準型別都自動實作了這兩個 trait。
| 類型 | Send | Sync | 說明 | 
|---|---|---|---|
| i32,f64,bool,String,Vec<T> | ✅ | ✅ | 值語意,可安全複製或共享 | 
| Rc<T> | ❌ | ❌ | 非 thread-safe RC | 
| Arc<T> | ✅ | ✅ | 原子計數,可跨線 | 
| Mutex<T> | ✅ | ✅ | 鎖住內部 T,保證排他存取 | 
| RefCell<T> | ❌ | ❌ | 僅限單執行緒 runtime 借用檢查 | 
| Cell<T> | ❌ | ❌ | 同上 | 
| *mut T/*const T | ❌ | ❌ | 原始指標,不具安全語義 | 
Rust 編譯器會自動根據型別內含內容決定能不能跨線。
這一點與 C++ 相反——在 C++,所有東西理論上都能跨線;在 Rust,只有被認證安全的才行。
要共享資料怎麼辦?在 Rust,你不能用 Rc,要用:
一起用時的典型寫法:
use std::sync::{Arc, Mutex};
use std::thread;
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..5 {
    let counter = Arc::clone(&counter);
    let handle = thread::spawn(move || {
        let mut num = counter.lock().unwrap();
        *num += 1;
    });
    handles.push(handle);
}
for h in handles { h.join().unwrap(); }
println!("result = {}", *counter.lock().unwrap());
Arc::new(Mutex::new(0))Arc::clone.lock()這種組合 (Arc<Mutex<T>>) 幾乎是所有跨線共享狀態的起點。
而且它自動實作 Send、Sync,所以不需要 unsafe。
Rust 的 borrow checker 會擋掉任何可能造成資料競爭的型別:
| 型別 | 為什麼不能跨線? | 
|---|---|
| Rc<T> | 非原子計數,多線 race | 
| RefCell<T> | 借用檢查在 runtime,不 thread-safe | 
| Cell<T> | 同上,只適合單線修改 | 
| *mut T/*const T | 原始指標,不知道誰擁有 | 
| &mut T | 可變引用只能唯一存在,跨線會破壞規則 | 
這些型別在單執行緒下非常好用(例如 GUI event loop 或 wasm),
但只要放進 thread::spawn,編譯器就會馬上報紅。
Rust 把「data race」變成編譯錯誤,而不是 runtime 驚喜。
這是 Rust 想讓你思考的問題。
在 C++,我們容易先開 thread 再想同步;
Rust 則鼓勵你先想「擁有權的分工」。
一個常見的 pattern 是:
Mutex<T>;Arc<T>;std::sync::mpsc 或 tokio channel)。Rust 的並行模型更接近「資料流分工」而不是「共享狀態爭奪」。
你不需要鎖全域變數,只需要把資料送給下一個擁有者。
| 面向 | C++ | Rust | 
|---|---|---|
| 執行緒建立 | 任意傳參 | move進閉包需滿足Send | 
| 鎖 | 任何 mutex 都能鎖任何物件 | 需 Send + 'static才能跨線 | 
| RC 計數 | shared_ptr不分執行緒 | Rc限單線,Arc限跨線 | 
| 借用檢查 | runtime/手動紀律 | 編譯期強制 | 
| 錯誤型態 | race → UB/crash | race → compile error |